/**
 * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "EglRenderContext.h"
#include "OpenGlUtils.h"
#include "napi_tool.h"
#include <cstring>
bool CheckEglExtension(const char *extensions, const char *extension)
{
    size_t extLen = strlen(extension);
    const char *end = extensions + strlen(extensions);

    while (extensions < end) {
        size_t n = 0;
        /* Skip whitespaces, if any */
        if (*extensions == ' ') {
            extensions++;
            continue;
        }

        n = strcspn(extensions, " ");
        /* Compare strings */
        if (n == extLen && strncmp(extension, extensions, n) == 0) {
            return true; /* Found */
        }
        extensions += n;
    }
    /* Not found */
    return false;
}

constexpr const char EGL_KHR_SURFACELESS_CONTEXT[] =
    "EGL_KHR_surfaceless_context";

using GetPlatformDisplayExt = PFNEGLGETPLATFORMDISPLAYEXTPROC;
constexpr const char *EGL_GET_PLATFORM_DISPLAY_EXT = "eglGetPlatformDisplayEXT";
// 获取当前的显示设备
static EGLDisplay GetPlatformEglDisplay(EGLenum platform, void *native_display,
                                        const EGLint *attrib_list)
{
    return eglGetDisplay((EGLNativeDisplayType)native_display);
}

EglRenderContext::EglRenderContext() {}

EglRenderContext::~EglRenderContext() noexcept
{
    if (eglDisplay_ == EGL_NO_DISPLAY) {
        return;
    }

    eglDestroyContext(eglDisplay_, eglContext_);
    eglMakeCurrent(eglDisplay_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglTerminate(eglDisplay_);
    eglReleaseThread();

    eglDisplay_ = EGL_NO_DISPLAY;
    eglContext_ = EGL_NO_CONTEXT;
}

bool EglRenderContext::initContext(void *window, int32_t width,
                                   int32_t height)
{
    if (isEglContextReady()) {
        return true;
    }
    eglWindow_ = reinterpret_cast<EGLNativeWindowType>(window);
    eglDisplay_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (eglDisplay_ == EGL_NO_DISPLAY) {
        LOGE("unable to get EGL display.");
        return -1;
    }

    EGLint eglMajVers;
    EGLint eglMinVers;
    if (!eglInitialize(eglDisplay_, &eglMajVers, &eglMinVers)) {
        eglDisplay_ = EGL_NO_DISPLAY;
        LOGE("unable to initialize display");
        return -1;
    }

    if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) {
        LOGE("Failed to bind OpenGL ES API, error: %{public}s.",
             OpenGlUtils::getEglErrorString().c_str());
        return false;
    }

    EGLint attribList[] = {EGL_SURFACE_TYPE,
                           EGL_WINDOW_BIT,
                           EGL_RED_SIZE,
                           8,
                           EGL_GREEN_SIZE,
                           8,
                           EGL_BLUE_SIZE,
                           8,
                           EGL_ALPHA_SIZE,
                           8,
                           EGL_RENDERABLE_TYPE,
                           EGL_OPENGL_ES3_BIT,
                           EGL_NONE};
    EGLint configsNum;

    if (!eglChooseConfig(eglDisplay_, attribList, &eglConfig_, 1, &configsNum)) {
        LOGE("eglChooseConfig ERROR");
        return -1;
    }

    /* Create EGLContext from */
    const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
    eglContext_ =
        eglCreateContext(eglDisplay_, eglConfig_, EGL_NO_CONTEXT, contextAttribs);
    if (eglContext_ == EGL_NO_CONTEXT) {
        LOGE("eglCreateContext ERROR");
        return false;
    }
    return true;
}

void EglRenderContext::makeCurrent(EGLSurface surface) const
{
    EGLSurface currSurface = surface;
    if (!eglMakeCurrent(eglDisplay_, currSurface, currSurface, eglContext_)) {
        EGLint surfaceId = -1;
        eglQuerySurface(eglDisplay_, currSurface, EGL_CONFIG_ID, &surfaceId);
        LOGE("EglRenderContext::MakeCurrent: Failed to make current on EGLSurface "
             "%{public}d, error is %{public}s.",
             surfaceId, OpenGlUtils::getEglErrorString().c_str());
    }
}

void EglRenderContext::swapBuffers(EGLSurface surface) const
{
    EGLBoolean ret = eglSwapBuffers(eglDisplay_, surface);
    if (ret == EGL_FALSE) {
        EGLint surfaceId = -1;
        eglQuerySurface(eglDisplay_, surface, EGL_CONFIG_ID, &surfaceId);
        LOGE("EglRenderContext::SwapBuffers: Failed to SwapBuffers on EGLSurface "
             "%{public}d, error is %{public}s.",
             surfaceId, OpenGlUtils::getEglErrorString().c_str());
    }
}

void EglRenderContext::destroyEglSurface(EGLSurface surface)
{
    if (!eglDestroySurface(eglDisplay_, surface)) {
        EGLint surfaceId = -1;
        eglQuerySurface(eglDisplay_, surface, EGL_CONFIG_ID, &surfaceId);
        LOGE("EglRenderContext::DestroyEglWindowSurface: Failed to DestroySurface "
             "on EGLSurface %{public}d, "
             "error is %{public}s.",
             surfaceId, OpenGlUtils::getEglErrorString().c_str());
    }
}

bool EglRenderContext::isEglContextReady() const
{
    return eglContext_ != EGL_NO_CONTEXT;
}

EGLSurface EglRenderContext::createEglSurface(const EGLint *attribList)
{
    if (!isEglContextReady()) {
        LOGE("EglRenderContext::CreateEglWindowSurface: EGL context has not "
             "initialized");
        return EGL_NO_SURFACE;
    }

    eglMakeCurrent(eglDisplay_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

    EGLSurface surface =
        eglCreateWindowSurface(eglDisplay_, eglConfig_, eglWindow_, attribList);
    if (surface == EGL_NO_SURFACE) {
        LOGE("EglRenderContext::CreateEglWindowSurface: Failed to create "
             "eglSurface, error is %{public}s.",
             OpenGlUtils::getEglErrorString().c_str());
        return EGL_NO_SURFACE;
    }

    return surface;
}

EGLContext EglRenderContext::getEGLContext() const { return eglContext_; }

EGLDisplay EglRenderContext::getEGLDisplay() const { return eglDisplay_; }